package com.mzj.saas.commons.util;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageInputStream;

import ij.ImagePlus;
import ij.process.Blitter;
import ij.process.ImageProcessor;

public class ImageUtils
{
	final static Map<String, ImageReaderSpi> imageReaderSpiMapping = new ConcurrentHashMap<String, ImageReaderSpi>() ;
	final static Map<String, ImageWriterSpi> imageWriterSpiMapping = new ConcurrentHashMap<String, ImageWriterSpi>() ;

	static
	{
		ImageIO.setUseCache(false) ;
	}

	public final static ImageReader getImageReader( InputStream in ) throws IOException
	{
		return getImageReader( in, null ) ;
	}

	public final static ImageReader getImageReader( InputStream in, String mimeType ) throws IOException
	{
		if( mimeType==null || mimeType.isEmpty() )
		{
			ImageInputStream imageIn = ImageIO.createImageInputStream(in) ;
			Iterator<ImageReader> iter =  ImageIO.getImageReaders( imageIn ) ;
			if( iter == null || !iter.hasNext() )
				throw new IllegalArgumentException( "No suitable image reader for " + mimeType ) ;
			
			ImageReader imageReader = iter.next() ;
			imageReader.setInput( imageIn );
			
			return imageReader ;
		}
		else
		{
			ImageReader imageReader = getImageReader( mimeType ) ;
			imageReader.setInput( ImageIO.createImageInputStream(in) );
			
			return imageReader ;
		}
	}
	
	public final static ImageWriter getImageWriter( OutputStream out, ImageReader imageReader ) throws IOException
	{
		ImageWriter imageWriter = ImageIO.getImageWriter( imageReader ) ;
		imageWriter.setOutput( ImageIO.createImageOutputStream( out ));
		
		return imageWriter ;
	}
	
	public final static ImageWriter getImageWriter( OutputStream out, String mimeType ) throws IOException
	{
		ImageWriter imageWriter = getImageWriter( mimeType ) ;
		imageWriter.setOutput( ImageIO.createImageOutputStream( out ));
		
		return imageWriter ;
	}

	private final static ImageReader getImageReader( String mimeType ) throws IOException
	{
		ImageReaderSpi readerSpi = imageReaderSpiMapping.get( mimeType ) ;
		if( readerSpi != null )
			return readerSpi.createReaderInstance() ;
		
		Iterator<ImageReader> iter = ImageIO.getImageReadersByMIMEType( mimeType ) ;
		if( iter == null || !iter.hasNext() )
			throw new IllegalArgumentException( "No suitable image reader for " + mimeType ) ;
		
		ImageReader imageReader = iter.next() ;
		imageReaderSpiMapping.put( mimeType, imageReader.getOriginatingProvider() ) ;
		
		return imageReader ;
	}
	
	private final static ImageWriter getImageWriter( String mimeType ) throws IOException
	{
		ImageWriterSpi writerSpi = imageWriterSpiMapping.get( mimeType ) ;
		if( writerSpi != null )
			return writerSpi.createWriterInstance() ;
		
		Iterator<ImageWriter> iter = ImageIO.getImageWritersByMIMEType( mimeType ) ;
		if( iter == null || !iter.hasNext() )
			throw new IllegalArgumentException( "No suitable image writer for " + mimeType ) ;
		
		ImageWriter imageWriter = iter.next() ;
		imageWriterSpiMapping.put( mimeType, imageWriter.getOriginatingProvider() ) ;

		return imageWriter ;
	}
	
	public final static void convert( ImageReader imageReader, ImageWriter imageWriter ) throws IOException
	{
		ImagePlus imagePlus = null ;

		try
		{
			imagePlus = new ImagePlus( null, imageReader.read(0) ) ;
			imageWriter.write( null, new IIOImage(imagePlus.getBufferedImage(), null, null), imageWriter.getDefaultWriteParam() ) ;
		}
		finally
		{
			try
			{
				if( imagePlus != null )
					imagePlus.close() ;
			}
			catch(Throwable err )
			{}
			
			try
			{
				if( imageReader != null )
					imageReader.dispose() ;
			}
			catch(Throwable err )
			{}
			
			try
			{
				if( imageWriter != null )
					imageWriter.dispose() ;
			}
			catch(Throwable err )
			{}
		}
	}
	
	public final static void scale( ImageReader imageReader, ImageWriter imageWriter, double scaleRate ) throws IOException
	{
		ImagePlus imagePlus = null ;

		try
		{
			imagePlus = new ImagePlus( null, imageReader.read(0) ) ;
			
			ImageProcessor ip = imagePlus.getProcessor() ;
			ip.setInterpolate(true);

			imagePlus.setProcessor(null, ip.resize( (int)(imagePlus.getWidth()*scaleRate), (int)(imagePlus.getHeight()*scaleRate)) );
			
			imageWriter.write( null, new IIOImage(imagePlus.getBufferedImage(), null, null), imageWriter.getDefaultWriteParam() ) ;
		}
		finally
		{
			try
			{
				if( imagePlus != null )
					imagePlus.close() ;
			}
			catch(Throwable err )
			{}
			
			try
			{
				if( imageReader != null )
					imageReader.dispose() ;
			}
			catch(Throwable err )
			{}
			
			try
			{
				if( imageWriter != null )
					imageWriter.dispose() ;
			}
			catch(Throwable err )
			{}
		}
	}

	public final static void reduce( InputStream in, OutputStream out, int width, float quality ) throws IOException
	{
		reduce( in, out, null, null, width, quality ) ;
	}
	
	public final static void reduce( InputStream in, OutputStream out, String mimeType, int width, float quality ) throws IOException
	{
		reduce( in, out, mimeType, null, width, quality ) ;
	}

	public final static void reduce( InputStream in, OutputStream out, String mimeType, String targetMimeType, int width, float quality ) throws IOException
	{
		ImageReader imageReader = getImageReader( in, mimeType ) ;
		ImageWriter imageWriter = targetMimeType==null||targetMimeType.isEmpty()||targetMimeType.equals(mimeType)?getImageWriter(out,imageReader):getImageWriter(out,targetMimeType) ;

		reduce( imageReader, imageWriter, width, quality ) ;
	}

	public final static void reduce( ImageReader imageReader, ImageWriter imageWriter, int width, float quality ) throws IOException
	{
		ImagePlus imagePlus = null ;

		try
		{
			imagePlus = new ImagePlus( null, imageReader.read(0) ) ;
			
			boolean scaleRequired = (imagePlus.getWidth()>width) ;
			if( scaleRequired )
			{
				double rate = width*1.0f/imagePlus.getWidth() ;
				ImageProcessor ip = imagePlus.getProcessor() ;
				ip.setInterpolate(true);
	
				imagePlus.setProcessor(null, ip.resize(width, (int)(imagePlus.getHeight()*rate)));
			}
			
			ImageWriteParam param = imageWriter.getDefaultWriteParam();
			if( param.canWriteCompressed() && quality<1.0 )
			{
				param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
				param.setCompressionQuality(quality);
			}
			
			imageWriter.write( null, new IIOImage(imagePlus.getBufferedImage(), null, null), param ) ;
		}
		finally
		{
			try
			{
				if( imagePlus != null )
					imagePlus.close() ;
			}
			catch(Throwable err )
			{}
			
			try
			{
				if( imageReader != null )
					imageReader.dispose() ;
			}
			catch(Throwable err )
			{}
			
			try
			{
				if( imageWriter != null )
					imageWriter.dispose() ;
			}
			catch(Throwable err )
			{}
		}
	}

	public final static void reduce( ImageReader imageReader, ImageWriter imageWriter, int width, int height, float quality ) throws IOException
	{
		ImagePlus imagePlus = null ;

		try
		{
			imagePlus = new ImagePlus( null, imageReader.read(0) ) ;
			
			boolean scaleRequired = (imagePlus.getWidth()>width || imagePlus.getHeight()>height) ;
			if( scaleRequired )
			{
				double widthScaleRate = width*1.0f/imagePlus.getWidth() ;
				double heightScaleRate = height*1.0f/imagePlus.getHeight() ;
				
				double rate = Math.min( widthScaleRate, heightScaleRate ) ;
				ImageProcessor ip = imagePlus.getProcessor() ;
				ip.setInterpolate(true);
	
				imagePlus.setProcessor(null, ip.resize( (int)(imagePlus.getWidth()*rate), (int)(imagePlus.getHeight()*rate)));
			}
			
			ImageWriteParam param = imageWriter.getDefaultWriteParam();
			if( param.canWriteCompressed() && quality<1.0 )
			{
				param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
				param.setCompressionQuality(quality);
			}
			
			imageWriter.write( null, new IIOImage(imagePlus.getBufferedImage(), null, null), param ) ;
		}
		finally
		{
			try
			{
				if( imagePlus != null )
					imagePlus.close() ;
			}
			catch(Throwable err )
			{}
			
			try
			{
				if( imageReader != null )
					imageReader.dispose() ;
			}
			catch(Throwable err )
			{}
			
			try
			{
				if( imageWriter != null )
					imageWriter.dispose() ;
			}
			catch(Throwable err )
			{}
		}
	}
	
	public final static void montage( List<ImageReader> imageReaders, ImageWriter imageWriter, int width, int height )
	{
		if( imageReaders==null || imageReaders.isEmpty() || imageWriter==null || width<=0 || height<=0 )
			throw new IllegalArgumentException() ;
		
		int imageCount = imageReaders.size() ;

		BufferedImage canvasImage = new BufferedImage( width, height, BufferedImage.TYPE_INT_RGB ) ;
		ImagePlus canvasImagePlus = new ImagePlus( null, canvasImage ) ;
		ImageProcessor canvasProcessor = canvasImagePlus.getProcessor() ;
		canvasProcessor.setColor( new Color(210,210,210) );
		canvasProcessor.fill();
		
		int margin = 2 ;
		int halfMargin = margin/2 ;
		int gridWidth,gridHeight ;
		ImagePlus imagePlus = null ;
		
		try
		{
			switch( imageCount )
			{
			case 1:
				imagePlus = new ImagePlus( null, imageReaders.get(0).read(0) ) ;
				gridWidth = width-2*margin ;
				gridHeight = height-2*margin ;
				fill( imagePlus, canvasImagePlus, margin, margin,gridWidth, gridHeight ) ;
				imagePlus.close();
				break ;
			case 2:
				gridWidth = (width-margin*3)/2 ;
				gridHeight = (height-margin*3)/2 ;
				
				imagePlus = new ImagePlus( null, imageReaders.get(0).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin, 2*margin+gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(1).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 2*margin+gridWidth, margin, gridWidth, gridHeight ) ;
				imagePlus.close();
				break ;
			case 3:
				gridWidth = (width-margin*3)/2 ;
				gridHeight = (height-margin*3)/2 ;
				
				imagePlus = new ImagePlus( null, imageReaders.get(0).read(0) ) ;
				fill( imagePlus, canvasImagePlus, width/2-gridWidth/2, margin, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(1).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin, gridHeight+2*margin, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(2).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 2*margin+gridWidth, gridHeight+2*margin, gridWidth, gridHeight ) ;
				imagePlus.close();
				break ;
			case 4:
				gridWidth = (width-margin*3)/2 ;
				gridHeight = (height-margin*3)/2 ;
				
				imagePlus = new ImagePlus( null, imageReaders.get(0).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin, margin, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(1).read(0) ) ;
				fill( imagePlus, canvasImagePlus, gridWidth+2*margin, margin, gridWidth, gridHeight ) ;
				imagePlus.close();
				
				imagePlus = new ImagePlus( null, imageReaders.get(2).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin, gridHeight+2*margin, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(3).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 2*margin+gridWidth, gridHeight+2*margin, gridWidth, gridHeight ) ;
				imagePlus.close();
				break ;
			case 5:
				gridWidth = (width-margin*4)/3 ;
				gridHeight = (height-margin*4)/3 ;
				
				imagePlus = new ImagePlus( null, imageReaders.get(0).read(0) ) ;
				fill( imagePlus, canvasImagePlus, width/2-halfMargin-gridWidth, height/2-halfMargin-gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(1).read(0) ) ;
				fill( imagePlus, canvasImagePlus, width/2+halfMargin, height/2-halfMargin-gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(2).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin, height/2+halfMargin, gridWidth, gridHeight ) ;
				imagePlus.close();
				
				imagePlus = new ImagePlus( null, imageReaders.get(3).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin*2+gridWidth, height/2+halfMargin, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(4).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin*3+2*gridWidth, height/2+halfMargin, gridWidth, gridHeight ) ;
				imagePlus.close();
				break ;
			case 6:
				gridWidth = (width-margin*4)/3 ;
				gridHeight = (height-margin*4)/3 ;
				
				imagePlus = new ImagePlus( null, imageReaders.get(0).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin, height/2-halfMargin-gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(1).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin*2+gridWidth, height/2-halfMargin-gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(2).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin*3+2*gridWidth, height/2-halfMargin-gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(3).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin, height/2+halfMargin, gridWidth, gridHeight ) ;
				imagePlus.close();
				
				imagePlus = new ImagePlus( null, imageReaders.get(4).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin*2+gridWidth, height/2+halfMargin, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(5).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin*3+2*gridWidth, height/2+halfMargin, gridWidth, gridHeight ) ;
				imagePlus.close();
				break ;
			case 7:
				gridWidth = (width-margin*4)/3 ;
				gridHeight = (height-margin*4)/3 ;
				
				imagePlus = new ImagePlus( null, imageReaders.get(0).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin, margin, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(1).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 2*margin+gridWidth, margin, gridWidth, gridHeight ) ;
				imagePlus.close();
				
				imagePlus = new ImagePlus( null, imageReaders.get(2).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 3*margin+2*gridWidth, margin, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(3).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin, 2*margin+gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(4).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 2*margin+gridWidth, 2*margin+gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();
				
				imagePlus = new ImagePlus( null, imageReaders.get(5).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 3*margin+2*gridWidth, 2*margin+gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(6).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 2*margin+gridWidth, 3*margin+2*gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();
				break ;
			case 8:
				gridWidth = (width-margin*4)/3 ;
				gridHeight = (height-margin*4)/3 ;
				
				imagePlus = new ImagePlus( null, imageReaders.get(0).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin, margin, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(1).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 2*margin+gridWidth, margin, gridWidth, gridHeight ) ;
				imagePlus.close();
				
				imagePlus = new ImagePlus( null, imageReaders.get(2).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 3*margin+2*gridWidth, margin, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(3).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin, 2*margin+gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(4).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 2*margin+gridWidth, 2*margin+gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();
				
				imagePlus = new ImagePlus( null, imageReaders.get(5).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 3*margin+2*gridWidth, 2*margin+gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();
				
				imagePlus = new ImagePlus( null, imageReaders.get(6).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin, 3*margin+2*gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(7).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 2*margin+gridWidth, 3*margin+2*gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();
				break ;
			default://equal or more than 9
				gridWidth = (width-margin*4)/3 ;
				gridHeight = (height-margin*4)/3 ;
				
				imagePlus = new ImagePlus( null, imageReaders.get(0).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin, margin, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(1).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 2*margin+gridWidth, margin, gridWidth, gridHeight ) ;
				imagePlus.close();
				
				imagePlus = new ImagePlus( null, imageReaders.get(2).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 3*margin+2*gridWidth, margin, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(3).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin, 2*margin+gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(4).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 2*margin+gridWidth, 2*margin+gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();
				
				imagePlus = new ImagePlus( null, imageReaders.get(5).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 3*margin+2*gridWidth, 2*margin+gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();
				
				imagePlus = new ImagePlus( null, imageReaders.get(6).read(0) ) ;
				fill( imagePlus, canvasImagePlus, margin, 3*margin+2*gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();

				imagePlus = new ImagePlus( null, imageReaders.get(7).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 2*margin+gridWidth, 3*margin+2*gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();
				
				imagePlus = new ImagePlus( null, imageReaders.get(8).read(0) ) ;
				fill( imagePlus, canvasImagePlus, 3*margin+2*gridWidth, 3*margin+2*gridHeight, gridWidth, gridHeight ) ;
				imagePlus.close();
				break ;
			}
			
			ImageWriteParam param = imageWriter.getDefaultWriteParam();
			imageWriter.write( null, new IIOImage(canvasImagePlus.getBufferedImage(), null, null), param ) ;
			canvasImagePlus.close();
		}
		catch( IOException err )
		{
			err.printStackTrace();
		}
	}
	
	private final static void fill( ImagePlus imagePlus, ImagePlus canvasImagePlus, int x, int y, int width, int height )
	{
		boolean scaleRequired = (imagePlus.getWidth()>width || imagePlus.getHeight()>height) ;
		if( scaleRequired )
		{
			double widthScaleRate = width*1.0f/imagePlus.getWidth() ;
			double heightScaleRate = height*1.0f/imagePlus.getHeight() ;
			
			double rate = Math.min( widthScaleRate, heightScaleRate ) ;
			ImageProcessor ip = imagePlus.getProcessor() ;
			ip.setInterpolate(true);
			imagePlus.setProcessor( ip.resize( (int)(imagePlus.getWidth()*rate), (int)(imagePlus.getHeight()*rate)) );
		}
		
		int actualWidth = imagePlus.getWidth() ;
		int actualHeight = imagePlus.getHeight() ;
		
		x += (width-actualWidth)/2 ;
		y += (height-actualHeight)/2 ;
		
		
		canvasImagePlus.getProcessor().copyBits( imagePlus.getProcessor(), x, y, Blitter.COPY );
	}
}
